#include "DumpLog.h"
#include <thread>
#include <iomanip>
#include <sstream>
#include <iostream>
#include <unistd.h>

namespace Log
{

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, ExitCode_E v)
{
    os->exitCode = v;
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, double v)
{
    std::ostringstream ss;
    ss << std::setprecision(16) << v;
    os->vecParam.push_back(ss.str());
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, float v)
{
    std::ostringstream ss;
    ss << std::setprecision(16) << v;
    os->vecParam.push_back(ss.str());
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, int8_t n)
{
    os->vecParam.push_back(std::to_string((int16_t)n));
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, int16_t n)
{
    os->vecParam.push_back(std::to_string(n));
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, int32_t n)
{
    os->vecParam.push_back(std::to_string(n));
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, int64_t n)
{
    os->vecParam.push_back(std::to_string(n));
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, uint8_t n)
{
    os->vecParam.push_back(std::to_string((uint16_t)n));
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, uint16_t n)
{
    os->vecParam.push_back(std::to_string(n));
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, uint32_t n)
{
    os->vecParam.push_back(std::to_string(n));
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, uint64_t n)
{
    os->vecParam.push_back(std::to_string(n));
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, const char *s)
{
    os->vecParam.push_back(s);
    return os;
}

std::shared_ptr<LogLine_t> &operator << (std::shared_ptr<LogLine_t> &os, const std::string &s)
{
    os->vecParam.push_back(s);
    return os;
}

DumpLog::DumpLog():
    colorReset_(""),
    colorRed_(""),
    colorGreen_(""),
    colorYellow_(""),
    isResetOut_(false), 
    needConsolePrint_(true), 
    exitCode_(ExitCode_E::OK_E), 
    stop_(false), 
    run_(true), 
    logFile_(""), 
    barUpIndex_(0), 
    barMaxIndex_(0), 
    barCurrentIndex_(0), 
    barTip_("")
{
    //1.
    if (isatty(fileno(stdout)))
    {
        colorReset_  = "\033[0m";
        colorRed_    = "\033[31m\033[1m";
        colorGreen_  = "\033[32m\033[1m";
        colorYellow_ = "\033[33m\033[1m";
    }
    else
    {
        isResetOut_  = true;
    }

    //2.
    std::thread t(&DumpLog::run, this);
    t.detach();
}

DumpLog::~DumpLog()
{
    //1.
    waitExit();

    //2.
    if (fw_.is_open())
    {
        fw_.close();
    }

    //3.
    std::cerr << "日志退出\n";
}

bool DumpLog::setLogFile(const std::string &f)
{
    //1.
    std::lock_guard<std::mutex> lock(mutexForFw_);

    //2.
    if (fw_.is_open())
    {
        fw_.close();
    }

    //3.
    fw_.open(f);

    //4.
    return fw_.is_open();
}

void DumpLog::setNeedConsolePrint(bool need)
{
    needConsolePrint_ = need;
}

ExitCode_E DumpLog::waitExit()
{
    //1.
    if (!run_.load(std::memory_order_relaxed))
    {
        return exitCode_;
    }

    //2.
    stop_.store(true, std::memory_order_relaxed);

    //3.
    while (run_.load(std::memory_order_relaxed))
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(32));
    }

    //4.
    return exitCode_;
}

void DumpLog::setBarMaxValue(uint32_t v, const std::string &tip)
{
    barTip_     = tip;
    barUpIndex_ = 0;
    barMaxIndex_.store(v, std::memory_order_relaxed);
    barCurrentIndex_.store(0, std::memory_order_relaxed);
}

void DumpLog::addBarValue(uint32_t v)
{
    if (v && barMaxIndex_.load(std::memory_order_relaxed))
    {
        barCurrentIndex_.store(barCurrentIndex_.load(std::memory_order_relaxed) + v, std::memory_order_relaxed);
        if (barCurrentIndex_.load(std::memory_order_relaxed) > barMaxIndex_.load(std::memory_order_relaxed))
        {
            barCurrentIndex_.store(barMaxIndex_.load(std::memory_order_relaxed), std::memory_order_relaxed);
        }

        auto ptr = std::make_shared<LogLine_t>();
        ptr->barIndex = barCurrentIndex_.load(std::memory_order_relaxed);
        pushLog(ptr);
    }
}

void DumpLog::stopBar()
{
    //1.
    const uint32_t v = barMaxIndex_.load(std::memory_order_relaxed);
    if (v == 0)
    {
        return;
    }

    //2.
    auto ptr = std::make_shared<LogLine_t>();
    ptr->barIndex = v;
    pushLog(ptr);
}

void DumpLog::pushLog(std::shared_ptr<LogLine_t> &s)
{
    std::lock_guard<std::mutex> lock(mutexForLine_);
    vecLogLine_.push_back(s);
}

void DumpLog::getCoutColor(std::string &red, std::string &green, std::string &yellow, std::string &reset) const
{
    red    = colorRed_;
    green  = colorGreen_;
    yellow = colorYellow_;
    reset  = colorReset_;
}

DumpLog &DumpLog::getGlobalLog()
{
    static DumpLog global;
    return global;
}

void DumpLog::run()
{
    //1.
    while (!stop_.load(std::memory_order_relaxed))
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(64));
        if (vecLogLine_.size())
        {
            dumpLog();
        }
    }
    
    //2.
    dumpLog();
    run_.store(false, std::memory_order_relaxed);
}

void DumpLog::dumpLog()
{
    //1.
    {
        std::lock_guard<std::mutex> lock(mutexForLine_);
        vecLogLine_.swap(vecUpdateLine_);
    }

    //2.
    if (needConsolePrint_)
    {
        std::lock_guard<std::mutex> lock(mutexForFw_);
        if (fw_.is_open())
        {
            printInAll();
        }
        else
        {
            printOnlyInConsole();
        }
    }
    else
    {
        std::lock_guard<std::mutex> lock(mutexForFw_);
        if (fw_.is_open())
        {
            printOnlyInFile();
        }
    }

    vecUpdateLine_.clear();
}

void DumpLog::printOnlyInConsole()
{
    std::size_t i(0), j(0);
    std::ostringstream ss;
    std::string timeStr(""), logTypeStr(""), otherLine("");
    for (auto &ptr : vecUpdateLine_)
    {
        //1.
        if (ptr->barIndex != -1)
        {
            printBar(ptr, true, false);
            continue;
        }
        updateExitCode(ptr);

        //2.
        ss.str("");
        std::time_t t = std::chrono::system_clock::to_time_t(ptr->time);
        std::tm* pt = std::localtime(&t);

        ss  << "[" << pt->tm_year + 1900 << "-";
        normNO(ss, pt->tm_mon + 1);

        ss << "-";
        normNO(ss, pt->tm_mday);

        ss << " ";
        normNO(ss, pt->tm_hour);
        ss << ":";
        normNO(ss, pt->tm_min);
        ss << ":";
        normNO(ss, pt->tm_sec);

        uint16_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(ptr->time.time_since_epoch()).count() % 1000;
        if (ms < 10)
        {
            ss << ".00";
        }
        else if(ms < 100)
        {
            ss << ".0";
        }
        else
        {
            ss << ".";
        }
        ss << ms << "][";
        timeStr = ss.str();
        std::cerr << colorReset_ << timeStr;

        //3.
        if (ptr->type == LogType_E::Info_E)
        {
            std::cerr << colorGreen_;
            logTypeStr = "Info";
        }
        else if(ptr->type == LogType_E::Warn_E)
        {
            std::cerr << colorYellow_;
            logTypeStr = "Warn";
        }
        else
        {
            std::cerr << colorRed_;
            logTypeStr = "Error";
        }
        std::cerr << logTypeStr << colorReset_;

        //4.
        ss.str("");
        ss << "][";

        i = ptr->cppFile.rfind('/');
        if (i != std::string::npos)
        {
            ++i;
            ss << ptr->cppFile.substr(i, ptr->cppFile.size() - i);
        }
        else
        {
            ss << ptr->cppFile;
        }

        ss << ":" << ptr->lineNO << "] ";

        //5.
        i = 0;
        for (auto &p : ptr->vecParam)
        {
            j = ptr->info.find("{}", i);
            if (j == std::string::npos)
            {
                break;
            }

            ss << ptr->info.substr(i, j - i) << p;
            i = j + 2;
        }

        if (i < ptr->info.size())
        {
            ss << ptr->info.substr(i, ptr->info.size() - i);
        }

        //6.
        ss << "\n";
        otherLine = ss.str();
        std::cerr << otherLine;
    }
}

void DumpLog::printOnlyInFile()
{
    std::size_t i(0), j(0);
    std::ostringstream ss;
    std::string timeStr(""), logTypeStr(""), otherLine("");
    for (auto &ptr : vecUpdateLine_)
    {
        //1.
        if (ptr->barIndex != -1)
        {
            printBar(ptr, false, true);
            continue;
        }
        updateExitCode(ptr);

        //2.
        ss.str("");
        std::time_t t = std::chrono::system_clock::to_time_t(ptr->time);
        std::tm* pt = std::localtime(&t);

        ss  << "[" << pt->tm_year + 1900 << "-";
        normNO(ss, pt->tm_mon + 1);

        ss << "-";
        normNO(ss, pt->tm_mday);

        ss << " ";
        normNO(ss, pt->tm_hour);
        ss << ":";
        normNO(ss, pt->tm_min);
        ss << ":";
        normNO(ss, pt->tm_sec);

        uint16_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(ptr->time.time_since_epoch()).count() % 1000;
        if (ms < 10)
        {
            ss << ".00";
        }
        else if(ms < 100)
        {
            ss << ".0";
        }
        else
        {
            ss << ".";
        }
        ss << ms << "][";
        timeStr = ss.str();

        //3.
        if (ptr->type == LogType_E::Info_E)
        {
            logTypeStr = "Info";
        }
        else if(ptr->type == LogType_E::Warn_E)
        {
            logTypeStr = "Warn";
        }
        else
        {
            logTypeStr = "Error";
        }

        //4.
        ss.str("");
        ss << "][";

        i = ptr->cppFile.rfind('/');
        if (i != std::string::npos)
        {
            ++i;
            ss << ptr->cppFile.substr(i, ptr->cppFile.size() - i);
        }
        else
        {
            ss << ptr->cppFile;
        }

        ss << ":" << ptr->lineNO << "] ";

        //5.
        i = 0;
        for (auto &p : ptr->vecParam)
        {
            j = ptr->info.find("{}", i);
            if (j == std::string::npos)
            {
                break;
            }

            ss << ptr->info.substr(i, j - i) << p;
            i = j + 2;
        }

        if (i < ptr->info.size())
        {
            ss << ptr->info.substr(i, ptr->info.size() - i);
        }

        //6.
        ss << "\n";
        otherLine = ss.str();

        //7.
        fw_ << timeStr << logTypeStr << otherLine;
    }

    fw_.flush();
}

void DumpLog::printInAll()
{
    std::size_t i(0), j(0);
    std::ostringstream ss;
    std::string timeStr(""), logTypeStr(""), otherLine("");
    for (auto &ptr : vecUpdateLine_)
    {
        //1.
        if (ptr->barIndex != -1)
        {
            printBar(ptr, true, true);
            continue;
        }
        updateExitCode(ptr);

        //2.
        ss.str("");
        std::time_t t = std::chrono::system_clock::to_time_t(ptr->time);
        std::tm* pt = std::localtime(&t);

        ss  << "[" << pt->tm_year + 1900 << "-";
        normNO(ss, pt->tm_mon + 1);

        ss << "-";
        normNO(ss, pt->tm_mday);

        ss << " ";
        normNO(ss, pt->tm_hour);
        ss << ":";
        normNO(ss, pt->tm_min);
        ss << ":";
        normNO(ss, pt->tm_sec);

        uint16_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(ptr->time.time_since_epoch()).count() % 1000;
        if (ms < 10)
        {
            ss << ".00";
        }
        else if(ms < 100)
        {
            ss << ".0";
        }
        else
        {
            ss << ".";
        }
        ss << ms << "][";
        timeStr = ss.str();
        std::cerr << colorReset_ << timeStr;

        //3.
        if (ptr->type == LogType_E::Info_E)
        {
            std::cerr << colorGreen_;
            logTypeStr = "Info";
        }
        else if(ptr->type == LogType_E::Warn_E)
        {
            std::cerr << colorYellow_;
            logTypeStr = "Warn";
        }
        else
        {
            std::cerr << colorRed_;
            logTypeStr = "Error";
        }
        std::cerr << logTypeStr << colorReset_;

        //3.
        ss.str("");
        ss << "][";

        i = ptr->cppFile.rfind('/');
        if (i != std::string::npos)
        {
            ++i;
            ss << ptr->cppFile.substr(i, ptr->cppFile.size() - i);
        }
        else
        {
            ss << ptr->cppFile;
        }

        ss << ":" << ptr->lineNO << "] ";

        //4.
        i = 0;
        for (auto &p : ptr->vecParam)
        {
            j = ptr->info.find("{}", i);
            if (j == std::string::npos)
            {
                break;
            }

            ss << ptr->info.substr(i, j - i) << p;
            i = j + 2;
        }

        if (i < ptr->info.size())
        {
            ss << ptr->info.substr(i, ptr->info.size() - i);
        }

        //5.
        ss << "\n";
        otherLine = ss.str();
        std::cerr << otherLine;

        //6.
        fw_ << timeStr << logTypeStr << otherLine;
    }

    fw_.flush();
}

void DumpLog::dispBar(bool consIsOK, bool fwIsOK, uint8_t n, uint16_t v)
{
    //1.
    std::string diffNstr("");
    if (fwIsOK)
    {
        if (barUpIndex_ == n)
        {
            fw_ << barTip_ << ":";
        }

        diffNstr = getProgBarStr(n);
        fw_ << diffNstr;
        if (v == 100)
        {
            fw_ << "\n";
        }
    }

    //2.
    if (consIsOK)
    {
        if (isResetOut_)
        {
            if (barUpIndex_ == n)
            {
                std::cerr << barTip_ << ":";
            }

            if (diffNstr.empty())
            {
                diffNstr = getProgBarStr(n);
            }

            std::cerr << diffNstr;
            if (v == 100)
            {
                std::cerr << "\n";
            }
        }
        else
        {
            std::ostringstream ss;
            ss << barTip_ << "(" << v << "%):" << getProgBarStr(barUpIndex_);
            if (v == 100)
            {
                ss << "\n";

            }
            
            std::cout << '\r' << ss.str() << std::flush;
        }
    }
}

void DumpLog::printBar(const std::shared_ptr<LogLine_t> &ptr, bool consIsOK, bool fwIsOK)
{
    //1.
    const uint32_t maxV = barMaxIndex_.load(std::memory_order_relaxed);
    if (maxV == 0)
    {
        return;
    }

    //2.
    else
    {
        uint8_t m;
        const uint32_t v = ptr->barIndex;
        if (v >= maxV)
        {
            m = 100 - barUpIndex_;
            barUpIndex_ = 100;
            dispBar(consIsOK, fwIsOK, m, 100);
            barMaxIndex_.store(0, std::memory_order_relaxed);
            barTip_ = "";
            barCurrentIndex_.store(0, std::memory_order_relaxed);
        }
        else
        {
            uint16_t n = (v * 100) / maxV;
            if (n > barUpIndex_)
            {
                m = n - barUpIndex_;
                barUpIndex_ = n;
                dispBar(consIsOK, fwIsOK, m, barUpIndex_);
            }
        }
    }
}

void DumpLog::updateExitCode(const std::shared_ptr<LogLine_t> &ptr)
{
    //1.
    if (ptr->exitCode == ExitCode_E::Nothing_E)
    {
        return;
    }

    //2.
    if(ptr->exitCode > exitCode_)
    {
        exitCode_ = ptr->exitCode;
    }
}

void DumpLog::normNO(std::ostringstream &ss, uint16_t id) const
{
    //1.
    if (id < 10)
    {
        ss << "0";
    }

    //2.
    ss << id;
}

std::string DumpLog::getProgBarStr(uint8_t n) const
{
    std::ostringstream ss;
    for (;n;--n)
    {
        ss << "▉";
    }
    

    return ss.str();
}

}